Crate naga

source ·
Expand description

Universal shader translator.

The central structure of the crate is Module. A Module contains:

  • Functions, which have arguments, a return type, local variables, and a body,

  • EntryPoints, which are specialized functions that can serve as the entry point for pipeline stages like vertex shading or fragment shading,

  • Constants and GlobalVariables used by EntryPoints and Functions, and

  • Types used by the above.

The body of an EntryPoint or Function is represented using two types:

  • An Expression produces a value, but has no side effects or control flow. Expressions include variable references, unary and binary operators, and so on.

  • A Statement can have side effects and structured control flow. Statements do not produce a value, other than by storing one in some designated place. Statements include blocks, conditionals, and loops, but also operations that have side effects, like stores and function calls.

Statements form a tree, with pointers into the DAG of Expressions.

Restricting side effects to statements simplifies analysis and code generation. A Naga backend can generate code to evaluate an Expression however and whenever it pleases, as long as it is certain to observe the side effects of all previously executed Statements.

Many Statement variants use the Block type, which is Vec<Statement>, with optional span info, representing a series of statements executed in order. The body of an EntryPoints or Function is a Block, and Statement has a Block variant.

If the clone feature is enabled, Arena, UniqueArena, Type, TypeInner, Constant, Function, EntryPoint and Module can be cloned.

Arenas

To improve translator performance and reduce memory usage, most structures are stored in an Arena. An Arena<T> stores a series of T values, indexed by Handle<T> values, which are just wrappers around integer indexes. For example, a Function’s expressions are stored in an Arena<Expression>, and compound expressions refer to their sub-expressions via Handle<Expression> values. (When examining the serialized form of a Module, note that the first element of an Arena has an index of 1, not 0.)

A UniqueArena is just like an Arena, except that it stores only a single instance of each value. The value type must implement Eq and Hash. Like an Arena, inserting a value into a UniqueArena returns a Handle which can be used to efficiently access the value, without a hash lookup. Inserting a value multiple times returns the same Handle.

If the span feature is enabled, both Arena and UniqueArena can associate a source code span with each element.

Function Calls

Naga’s representation of function calls is unusual. Most languages treat function calls as expressions, but because calls may have side effects, Naga represents them as a kind of statement, Statement::Call. If the function returns a value, a call statement designates a particular Expression::CallResult expression to represent its return value, for use by subsequent statements and expressions.

Expression evaluation time

It is essential to know when an Expression should be evaluated, because its value may depend on previous Statements’ effects. But whereas the order of execution for a tree of Statements is apparent from its structure, it is not so clear for Expressions, since an expression may be referred to by any number of Statements and other Expressions.

Naga’s rules for when Expressions are evaluated are as follows:

  • Constant expressions are considered to be implicitly evaluated before execution begins.

  • FunctionArgument and LocalVariable expressions are considered implicitly evaluated upon entry to the function to which they belong. Function arguments cannot be assigned to, and LocalVariable expressions produce a pointer to the variable’s value (for use with Load and Store). Neither varies while the function executes, so it suffices to consider these expressions evaluated once on entry.

  • Similarly, GlobalVariable expressions are considered implicitly evaluated before execution begins, since their value does not change while code executes, for one of two reasons:

    • Most GlobalVariable expressions produce a pointer to the variable’s value, for use with Load and Store, as LocalVariable expressions do. Although the variable’s value may change, its address does not.

    • A GlobalVariable expression referring to a global in the AddressSpace::Handle address space produces the value directly, not a pointer. Such global variables hold opaque types like shaders or images, and cannot be assigned to.

  • A CallResult expression that is the result of a Statement::Call, representing the call’s return value, is evaluated when the Call statement is executed.

  • Similarly, an AtomicResult expression that is the result of an Atomic statement, representing the result of the atomic operation, is evaluated when the Atomic statement is executed.

  • All other expressions are evaluated when the (unique) Statement::Emit statement that covers them is executed.

Now, strictly speaking, not all Expression variants actually care when they’re evaluated. For example, you can evaluate a BinaryOperator::Add expression any time you like, as long as you give it the right operands. It’s really only a very small set of expressions that are affected by timing:

  • Load, ImageSample, and ImageLoad expressions are influenced by stores to the variables or images they access, and must execute at the proper time relative to them.

  • Derivative expressions are sensitive to control flow uniformity: they must not be moved out of an area of uniform control flow into a non-uniform area.

  • More generally, any expression that’s used by more than one other expression or statement should probably be evaluated only once, and then stored in a variable to be cited at each point of use.

Naga tries to help back ends handle all these cases correctly in a somewhat circuitous way. The ModuleInfo structure returned by Validator::validate provides a reference count for each expression in each function in the module. Naturally, any expression with a reference count of two or more deserves to be evaluated and stored in a temporary variable at the point that the Emit statement covering it is executed. But if we selectively lower the reference count threshold to one for the sensitive expression types listed above, so that we always generate a temporary variable and save their value, then the same code that manages multiply referenced expressions will take care of introducing temporaries for time-sensitive expressions as well. The Expression::bake_ref_count method (private to the back ends) is meant to help with this.

Expression scope

Each Expression has a scope, which is the region of the function within which it can be used by Statements and other Expressions. It is a validation error to use an Expression outside its scope.

An expression’s scope is defined as follows:

  • The scope of a Constant, GlobalVariable, FunctionArgument or LocalVariable expression covers the entire Function in which it occurs.

  • The scope of an expression evaluated by an Emit statement covers the subsequent expressions in that Emit, the subsequent statements in the Block to which that Emit belongs (if any) and their sub-statements (if any).

  • The result expression of a Call or Atomic statement has a scope covering the subsequent statements in the Block in which the statement occurs (if any) and their sub-statements (if any).

For example, this implies that an expression evaluated by some statement in a nested Block is not available in the Block’s parents. Such a value would need to be stored in a local variable to be carried upwards in the statement tree.

Modules

  • Backend functions that export shader Modules into binary and text formats.
  • Frontend parsers that consume binary and text shaders and load them into Modules.
  • Lists of reserved keywords for each shading language with a frontend or backend.
  • Module processing functionality.
  • Shader validator.

Structs

Enums

Constants

Type Definitions

  • Number of bytes per scalar.
  • Hash map that is faster but not resilient to DoS attacks.
  • Hash set that is faster but not resilient to DoS attacks.
  • A source code span together with “context”, a user-readable description of what part of the error it refers to.